#include "SQLiteCursor.h"
#include "SQLiteBlobIO.h"
#include "SQLiteDatabase.h"
#include "SQLiteException.h"
#include "../../threading/Thread.h"
#include "../../lang/TypeInfo.h"
#include "../../util/DateTime.h"
#include "../../collections/BinBuf.h"
#include "../../common/ObjPtrT.h"
#include "../../common/UtilFuncT.h"
#include "sqlite3.h"
#include <stdio.h>

using namespace cppflib::lang;
using namespace cppflib::threading;
using namespace cppflib::util;
using namespace cppflib::collections;

namespace cppflib
{

namespace database
{

namespace sqlite
{

PRIVATE sqlite3_stmt * __GetStmt(ptrany_t pStmt)
{
   return static_cast<sqlite3_stmt*>(pStmt);
}

/**
 *  Execute the prepared statement
 */
bool SQLiteCursor::EvaluateSqlStmt(ptrany_t pStmt, bool isNonQuery)
{
   const int RETRYTIMES = 3;
   int nrRetry = 0;
   bool isOK = false;

   do {
      int ret = sqlite3_step(__GetStmt(pStmt));
      if (isNonQuery) {
         if (ret == SQLITE_DONE) {
            isOK = true;
            break;
         }
      }
      else {
         // a query statement (from a cursor)
         if (ret == SQLITE_ROW) {
            isOK = true;
            break;
         }
      }

      if (ret == SQLITE_BUSY) {
         if (nrRetry >= RETRYTIMES)
            break;

         ++nrRetry;
         Thread::Sleep(200); // sleep a while
      }
      else {
         break;
      }
   } while(true);

   return isOK;
}

SQLiteCursor::SQLiteCursor(void)
{
   this->pStmt = NULL;
   this->pDB = NULL;
   this->pColName2IndexCache = NULL;
}

SQLiteCursor::SQLiteCursor(ptrany_t pStmt, SQLiteDatabase * pDB)
{
   this->pStmt = pStmt;
   this->pDB = pDB;
   cppmempool::Retain(this->pDB);
   this->pColName2IndexCache = NULL;
}

SQLiteCursor::~SQLiteCursor(void)
{
   Close();
}

/**
 *  Call this repeatedly until last row of data is returned
 *
 *  @return true -- one row of data is available for reading
 *  @return false -- no more row is available
 */
bool SQLiteCursor::Read()
{
   return EvaluateSqlStmt(pStmt, false);
}

void SQLiteCursor::Close()
{
   if (pStmt) {
      sqlite3_finalize(__GetStmt(pStmt));
      pStmt = NULL;
   }

   if (pDB) {
      cppmempool::Release(pDB);
      pDB = NULL;
   }

   if (pColName2IndexCache) {
      cppmempool::Release(pColName2IndexCache);
      pColName2IndexCache = NULL;
   }
}

/**
 *  Get number of column returned for this query
 */
int SQLiteCursor::GetColumnCount() const
{
   return sqlite3_column_count(__GetStmt(pStmt));
}

void SQLiteCursor::GetColumnType(int index, TypeInfo& typeInfo) const
{
   const char * dataType = sqlite3_column_decltype(__GetStmt(pStmt), index);
   
   if (!dataType)
      return;

   CFString dt(dataType);
   dt.MakeUpper();

   if (dt.Find(_S("BIGINT")) >= 0)
      typeInfo = TypeInfo(typeid(Int64));
   else if (dt.Find(_S("INT")) >= 0)
      typeInfo = TypeInfo(typeid(Int32));

   else if (dt.Find(_S("CHAR")) >= 0)
      typeInfo = TypeInfo(typeid(CFString));
   else if (dt.Find(_S("CLOB")) >= 0)
      typeInfo = TypeInfo(typeid(CFString));
   else if (dt.Find(_S("TEXT")) >= 0)
      typeInfo = TypeInfo(typeid(CFString));

   else if (dt.Find(_S("FLOAT")) >= 0)
      typeInfo = TypeInfo(typeid(Double));
   else if (dt.Find(_S("REAL")) >= 0)
      typeInfo = TypeInfo(typeid(Double));
   else if (dt.Find(_S("DOUB")) >= 0)
      typeInfo = TypeInfo(typeid(Double));

   else if (dt.Find(_S("DATE")) >= 0)
      typeInfo = TypeInfo(typeid(DateTime));
   else if (dt.Find(_S("TIME")) >= 0)
      typeInfo = TypeInfo(typeid(DateTime));
   else if (dt.Find(_S("TIMESTAMP")) >= 0)
      typeInfo = TypeInfo(typeid(DateTime));

   else if (dt.Find(_S("BLOB")) >= 0)
      typeInfo = TypeInfo(typeid(BinBuf));
   
   else // can't recognize, return the raw name
      typeInfo = TypeInfo(dt);
}


/**
 *  Get column index from a name
 *
 *  @return -1 if not found
 */
int SQLiteCursor::GetColumnIndexFromName(const CFString &colName, bool isThrowExpIfNotFound)
{
   if (pColName2IndexCache == NULL) {
      pColName2IndexCache = new HashtableT<CFString, Int32>();
      cppmempool::Retain(pColName2IndexCache);
   }

   Int32 *pVal = pColName2IndexCache->Get(colName);
   if (pVal)
      return pVal->Value();

   u32_t columnCount = GetColumnCount();
   if (pVal == NULL && pColName2IndexCache->GetCount() < columnCount) {
      CFString n;
      for (int i = 0; i < static_cast<int>(columnCount); ++i) {
         GetColumnName(i, n);
         if (colName == n) {
            // add to cache
            ObjPtrT<CFString> newName(new CFString(n));
            ObjPtrT<Int32> newIndex(new Int32(i));
            pColName2IndexCache->Set(newName, newIndex);
            return i;
         }
      }
   }

   if (isThrowExpIfNotFound) {
      CFString errStr;
      errStr.Format(_S("Column name '%s' not found"), static_cast<pcwstr_t>(colName)); 
      throw SQLiteException(errStr);
   }

   return -1; // not found
}

/**
 *  Get type of a column
 *
 *  @return NULL if select column is not an actual column of a table
 */
const TypeInfo * SQLiteCursor::GetColumnType(int index) const
{
   TypeInfo * t = new TypeInfo(CFString::Empty());
   GetColumnType(index, *t);
   return t;
}

/**
 *  Get column name
 */
const CFString * SQLiteCursor::GetColumnName(int index) const
{
   const char * name = sqlite3_column_name(__GetStmt(pStmt), index);
   return name ? new CFString(name) : NULL;
}

/**
 *  Get column name
 */
void SQLiteCursor::GetColumnName(int index, CFString &colName) const
{
   const char * name = sqlite3_column_name(__GetStmt(pStmt), index);
   colName = name ? name : "";
}

/**
 *  Get table name of a column
 */
const CFString * SQLiteCursor::GetTableName(int index) const
{
   const char * name = sqlite3_column_table_name(__GetStmt(pStmt), index);
   return name ? new CFString(name) : NULL;
}

/**
 *  Get table name of a column
 */
void SQLiteCursor::GetTableName(int index, CFString &tabName) const
{
   const char * name = sqlite3_column_table_name(__GetStmt(pStmt), index);
   tabName = name ? name : "";
}

/**
 *  Get database name
 */
const CFString * SQLiteCursor::GetDatabaseName(int index) const
{
   const char * name = sqlite3_column_database_name(__GetStmt(pStmt), index);
   return name ? new CFString(name) : NULL;
}

/**
 *  Get database name
 */
void SQLiteCursor::GetDatabaseName(int index, CFString &dbName) const
{
   const char * name = sqlite3_column_database_name(__GetStmt(pStmt), index);
   dbName = name ? name : "";
}

/**
 *  Check whether a field is null for the current row
 */
bool SQLiteCursor::IsNull(int index)
{
   return (sqlite3_column_bytes16(__GetStmt(pStmt), index) == 0);
}

bool SQLiteCursor::IsNull(const CFString &colName)
{
   return IsNull(GetColumnIndexFromName(colName, true));
}

/**
 *  Get number of bytes
 */
int SQLiteCursor::GetByteCount(int index)
{
   return sqlite3_column_bytes16(__GetStmt(pStmt), index);
}

int SQLiteCursor::GetByteCount(const CFString &colName)
{
   return GetByteCount(GetColumnIndexFromName(colName, true));
}

/**
 *  Get blob data
 *
 *  @param [in] blobOffset -- offset of blob data to fetch
 *  @param [in] fetchLength -- length of data to fetch. Pass 0 to fetch all
 *  @param [in, out] binBuf -- buffer to hold the fetched data
 *
 *  @return number of bytes fetched
 */
int SQLiteCursor::GetBlob(int index, int blobOffset, sz_t fetchLength, BinBuf & binBuf)
{
   int nrBytesRead = 0;
   int nrBlobBytes = GetByteCount(index);
   
   if (nrBlobBytes > 0 && 
            (blobOffset >= 0 && blobOffset < nrBlobBytes)) {

      if (fetchLength == 0)
         fetchLength = nrBlobBytes;
      
      nrBytesRead = nrBlobBytes - blobOffset;
      // take the less length
      nrBytesRead = cppflib::Min<int>(static_cast<int>(fetchLength), nrBytesRead);

      const u8_t * pData = static_cast<const u8_t*>(sqlite3_column_blob(__GetStmt(pStmt), index));
      binBuf.CopyData(pData + blobOffset, nrBytesRead);  // clear previous data
   }

   return nrBytesRead;
}

int SQLiteCursor::GetBlob(const CFString &colName, int blobOffset, sz_t fetchLength, collections::BinBuf & binBuf)
{
   return GetBlob(GetColumnIndexFromName(colName, true), blobOffset, fetchLength, binBuf);
}

/**
 *  Fetch all blob data
 */
int SQLiteCursor::GetBlob(int index, BinBuf & binBuf)
{
   return GetBlob(index, 0, 0, binBuf);
}


int SQLiteCursor::GetBlob(const CFString &colName, collections::BinBuf & binBuf)
{
   return GetBlob(GetColumnIndexFromName(colName, true), 0, 0, binBuf);
}

/**
 *  Get BlobIO to read/write blob data
 *
 *  Note: the original query statement must have included 'rowid' as one of its select column
 *
 *  @return NULL if no 'rowid' column found
 */
SQLiteBlobIO * SQLiteCursor::GetBlobIO(int index)
{
   // prepare blob column name
   CFString colName;
   GetColumnName(index, colName);
   
   return GetBlobIO(colName, index);
}

/**
 *  Get BlobIO by column name
 *
 *  Note: the original query statement must have included 'rowid' as one of its select column
 *
 *  @param [in] blobColName -- column name of blob
 *  @param [in] index (optional) -- to specify the index of the blob column
 *
 *  @return NULL if no 'rowid' column found
 */
SQLiteBlobIO * SQLiteCursor::GetBlobIO(const CFString &blobColName, int index)
{
   // find column 'rowid' first
   int rowIDCol = GetColumnIndexFromName(CFString(SQLiteDatabase::GetConstName(SQLiteDatabase::ROWID)), true);   

   i64_t rowID;
   if (!GetInt64(rowIDCol, rowID))
      return NULL;

   // prepare names
   CFString dbName, tabName, colName;
   GetDatabaseName(index, dbName);
   GetTableName(index, tabName);

   SQLiteBlobIO *pBlobIO = new SQLiteBlobIO(static_cast<SQLiteDatabase*>(pDB));
   if (!pBlobIO->Open(dbName, tabName, blobColName, rowID, false)) { // return a writable BlobIO
      cppmempool::Release(pBlobIO);
      return NULL;
   }

   return pBlobIO;
}

/**
 *  Get number of UTF16 characters (surrogate pairs are 2 characters) if this field is a string
 */
int SQLiteCursor::GetUTF16CharCount(int index)
{
   return GetByteCount(index) / sizeof(wc_t);
}

int SQLiteCursor::GetUTF16CharCount(const CFString &colName)
{
   return GetUTF16CharCount(GetColumnIndexFromName(colName, true));
}

/**
 *  Get string
 *
 *  @param [in] strOffset -- offset of string to fetch
 *  @param [in] fetchLength -- length of string to fetch. Pass 0 to fetch all
 *  @param [in, out] s -- buffer to hold the fetched string
 *
 *  @return number of UTF-16 characters fetched
 */
int SQLiteCursor::GetString(int index, int strOffset, sz_t fetchLength, CFString &s)
{
   int nrCharsRead = 0;
   int nrStrChars = GetUTF16CharCount(index);
   
   if (nrStrChars > 0 && 
            (strOffset >= 0 && strOffset < nrStrChars)) {

      if (fetchLength == 0)
         fetchLength = nrStrChars;
      
      nrCharsRead = nrStrChars - strOffset;
      // take the less length
      nrCharsRead = cppflib::Min(static_cast<int>(fetchLength), nrCharsRead);

      pcwstr_t pStr = static_cast<pcwstr_t>(sqlite3_column_text16(__GetStmt(pStmt), index));
      s = pStr; // clear previous data
   }

   return nrCharsRead;
}

int SQLiteCursor::GetString(const CFString &colName, int strOffset, sz_t fetchLength, CFString &s)
{
   return GetString(GetColumnIndexFromName(colName, true), strOffset, fetchLength, s);
}  

/**
 *  Fetch the whole string
 */
int SQLiteCursor::GetString(int index, CFString &s)
{
   return GetString(index, 0, 0, s);
}

int SQLiteCursor::GetString(const CFString &colName, CFString &s)
{
   return GetString(GetColumnIndexFromName(colName, true), s);
}

PRIVATE bool __AssignDateTime(DateTime &val, int year, int month, int day,
                                             int hour, int min, int sec)
{
   bool isOK = true;
   if (year >= 0 && month >= 0 && day >= 0 && hour >= 0 && min >= 0 && sec >= 0)
      val = DateTime(year, month, day, hour, min, sec, DateTime::DTK_UTC);
   else if (hour >= 0 && min >= 0 && sec >= 0)
      val = DateTime(0, 0, 0, hour, min, sec, DateTime::DTK_UTC);
   else
      isOK = false;

   return isOK;
}

/**
 *  Get Date time. SQLite can recognize the following datetime format
 *
 *  1. YYYY-MM-DD
 *  2. YYYY-MM-DD HH:MM
 *  3. YYYY-MM-DD HH:MM:SS
 *  4. YYYY-MM-DD HH:MM:SS.SSS
 *  5. YYYY-MM-DDTHH:MM
 *  6. YYYY-MM-DDTHH:MM:SS
 *  7. YYYY-MM-DDTHH:MM:SS.SSS
 *  8. HH:MM
 *  9. HH:MM:SS
 *  10. HH:MM:SS.SSS 
 */
bool SQLiteCursor::GetDateTime(int index, DateTime &val)
{
   CFString dt;
   GetString(index, dt);
   if (dt.IsEmpty())
      return false;

   const int BUFSIZE = 40;
   const int NRARG_DATETIME = 6;
   const int NRARG_TIMEONLY = 3;

   char tempDateTime[BUFSIZE];
   dt.ToUtf8String(tempDateTime, BUFSIZE, NULL);

   bool isOK = false;
   int year = -1, month = -1, day = -1, hour = -1, min = -1, sec = -1;
       
   if (dt.Find(_S('T')) > 0) {
      sscanf(tempDateTime, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &min, &sec);
      isOK = __AssignDateTime(val, year, month, day, hour, min, sec);
   }
   else {
      int nrScanned = sscanf(tempDateTime, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec);
      if (nrScanned > 2) {
         isOK = __AssignDateTime(val, year, month, day, hour, min, sec);
      }
      else {
         sscanf(tempDateTime, "%d:%d:%d", &hour, &min, &sec);
         isOK = __AssignDateTime(val, -1, -1, -1, hour, min, sec);
      }
   }

   return isOK;
}

bool SQLiteCursor::GetDateTime(const CFString &colName, util::DateTime &val)
{
   return GetDateTime(GetColumnIndexFromName(colName, true), val);
}

/**
 *  Get double
 */
bool SQLiteCursor::GetDouble(int index, double &val)
{
   val = sqlite3_column_double(__GetStmt(pStmt), index);
   return true;
}

bool SQLiteCursor::GetDouble(const CFString &colName, double &val)
{
   return GetDouble(GetColumnIndexFromName(colName, true), val);
}

/**
 *  Get Int32
 */
bool SQLiteCursor::GetInt32(int index, int &val)
{
   val = sqlite3_column_int(__GetStmt(pStmt), index);
   return true;
}

bool SQLiteCursor::GetInt32(const CFString &colName, int &val)
{
   return GetInt32(GetColumnIndexFromName(colName, true), val);
}

/**
 *  Get Int64
 */
bool SQLiteCursor::GetInt64(int index, i64_t &val)
{
   val = sqlite3_column_int64(__GetStmt(pStmt), index);
   return true;
}

bool SQLiteCursor::GetInt64(const CFString &colName, i64_t &val)
{
   return GetInt64(GetColumnIndexFromName(colName, true), val);
}

} // end of namespace sqlite

} // end of namespace database

} // end of namespace cppflib
